/* Bring in gd library functions */
#include "gd.h"
#include "sys/time.h"
 
/* Bring in standard I/O so we can output the PNG to a file */
#include <time.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>

#define MAX_WIDTH 300
#define MAX_HEIGHT 100
char *font = "/usr/share/fonts/mine/msyh.ttf";
int bg_color = 0;
int image_data[MAX_WIDTH][MAX_HEIGHT];

void set_random_seed();
int randint(int a, int b);
gdImagePtr generateCaptcha(int width, int height, int factor1, int factor2);
void get_delta_1(int* delta_x, int length, int fatcor);
void get_delta_2(int* delta_x, int length, int fatcor);
void vertical_distort(gdImagePtr image, int factor);
void horizontal_distort(gdImagePtr image, int factor);
void smooth(gdImagePtr image);
 
int main(int argc, char **argv) {
	set_random_seed();

	gdImagePtr image;
	FILE *pngout, *jpegout, *gifout;
	 
	/* Allocate the image: 64 pixels across by 64 pixels tall */
	int width = 264, height = 64;
	int factor1 = 20, factor2 = 20;
	if(argc >= 2){
		factor1 = factor2 = atoi(argv[1]);
	}
	if(argc >= 3){
		factor2 = atoi(argv[2]);
	}

	image = generateCaptcha(width, height, factor1, factor2);
	if( !image ){
		return 1;
	}

	pngout = fopen("test.png", "wb");
	jpegout = fopen("test.jpg", "wb");
	gifout = fopen("test.gif", "wb");
	 
	gdImagePng(image, pngout);
	/*Using the default JPEG quality setting. */
	gdImageJpeg(image, jpegout, -1);
	gdImageGif(image, gifout);
	 
	fclose(pngout);
	fclose(jpegout);
	fclose(gifout);
	 
	gdImageDestroy(image);
	return 0;
}
gdImagePtr generateCaptcha(int width, int height, int factor1, int factor2)
{
	gdImagePtr image = gdImageCreate(width, height);
 
	bg_color = gdImageColorAllocate(image, 0, 0, 0);
	int red = gdImageColorAllocate(image, 255, 0, 0);
	int brect[8];
	char *s = "hello, world";
	char *err;
	err = gdImageStringFT(image, brect, red, font, (double)20.0, (double)0.0, 20, 50, s);
	if(err) {printf("%s\n", err); return NULL;}

	vertical_distort(image, factor1);
	horizontal_distort(image, factor2);
	smooth(image);
	return image;
}

void smooth(gdImagePtr image)
{
	int x, y, color, red = 0, blue = 0, green = 0,
		width = gdImageSX(image),
		height = gdImageSY(image);
	int p[5] = {1.9, 0.3, 0.3, 0.3, 0.3};

	#define sum_color(x,y,p) color = gdImageGetPixel(image, x, y);\
		red += gdImageRed(image, color) * p;\
		green += gdImageGreen(image, color) * p; \
		blue += gdImageBlue(image, color) * p;

	for( x = 1; x < width - 1; x++ ){
		for( y = 1; y < height - 1; y++ ){
			sum_color( x, y, p[0] );
			sum_color( x - 1, y, p[1] )
			sum_color( x, y - 1, p[2] )
			sum_color( x + 1, y, p[3] )
			sum_color( x, y + 1, p[4] )
			red = red / 5;
			green = green / 5;
			blue = blue / 5;
			color = gdImageColorExact(image, red, green, blue);
			if(color < 0)
				color = gdImageColorAllocate(image, red, green, blue);
			image_data[x][y] = color;
			red = green = blue = 0;
		}
	}
	for( x = 1; x < width - 1; x++ ){
		for( y = 1; y < height - 1; y++ ){
			gdImageSetPixel(image, x, y, image_data[x][y]);
		}
	}

}
void vertical_distort(gdImagePtr image, int factor)
{
	int x, y, t;
	int delta[MAX_HEIGHT];
	int height = gdImageSY(image), width = gdImageSX(image);
	get_delta_1( delta, height, factor );
    for(y = 0; y < height; y++){
        if(delta[y] >= 0){
            for(x = width - 1, t = x - delta[y]; x >= delta[y]; x--, t--){
				gdImageSetPixel(image, x, y, gdImageGetPixel(image, t, y));
            }
            while(x >= 0){
				gdImageSetPixel(image, x, y, bg_color);
                x--;
            }
        }
        else{
            for(x = 0, t = x - delta[y]; x < width + delta[y]; x++, t++){
				gdImageSetPixel(image, x, y, gdImageGetPixel(image, t, y));
            }
            while(x < width){
				gdImageSetPixel(image, x, y, bg_color);
                x++;
            }
        }
    }
}
void horizontal_distort(gdImagePtr image, int factor)
{
	int x, y, t;
	int delta[MAX_WIDTH];
	int height = gdImageSY(image), width = gdImageSX(image);
	get_delta_2( delta, width, factor );
	/*
	printf("%d,%d\n", width, height);
	for(i = 0; i < height; i++){
		printf("%d ", delta[i]);
	}
	printf("\n");
	*/
    for(x = 0; x < width; x++){
        if(delta[x] >= 0){
            for(y = height - 1, t = y - delta[x]; y >= delta[x]; y--, t--){
				gdImageSetPixel(image, x, y, gdImageGetPixel(image, x, y));
            }
            while(y >= 0){
				gdImageSetPixel(image, x, y, bg_color);
                y--;
            }
        }
        else{
            for(y = 0, t = y - delta[x]; y < width + delta[x]; y++, t++){
				gdImageSetPixel(image, x, y, gdImageGetPixel(image, x, t));
            }
            while(y < width){
				gdImageSetPixel(image, x, y, bg_color);
                y++;
            }
        }
    }
}
void get_delta_1(int* delta, int length, int factor)
{
	int p[3] = {0, 0, 0};
	int i;
	double t;

	p[1] = randint(-factor, factor);
	printf("%d %d\n", factor, p[1]);
	for(i = 0; i < length; i++){
		t = i / (double)length;
		delta[i] = (int)((1 - t) * (1 - t) * (1 - t) * p[0] +
			3 * t * (1 - t) * p[1] +
			t * t * t * p[2]);
	}
}

void get_delta_2(int* delta, int length, int factor){
	#define min_curvature  7
	#define max_curvature  12
    int offset = randint(-factor, factor);
    int a = randint(min_curvature, max_curvature);
    int i;
    double t;
    double b = drand48() * 2 * M_PI;
    for(i = 0; i < length; i++){
        t = i / (double)length;
        delta[i] = (int)(offset * sin(a * t + b));
    }
}

int randint(int a, int b){
    return rand() % (b - a + 1) + a;
}
void set_random_seed(){
    struct timeval tv;
    struct timezone tz;
    long seed;
    gettimeofday(&tv, &tz);
    seed = tv.tv_usec;
    srand((unsigned)seed);
}

